/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2015-2016 ForgeRock AS.
 */

package org.forgerock.openam.core.rest.devices.oath;

import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.forgerock.openam.core.rest.devices.DeviceSettings;
import org.forgerock.util.Reject;

/**
 * Data model representation of an OATH device's settings.
 *
 * @since 13.0.0
 */
public final class OathDeviceSettings extends DeviceSettings {

    private String sharedSecret;
    private String deviceName;
    private long lastLogin;
    private int counter;
    private boolean checksumDigit = false;
    private int truncationOffset = 0;
    private int clockDriftSeconds = 0;

    /**
     * Empty no-arg constructor for Jackson usage, due to presence of non-default constructor.
     */
    public OathDeviceSettings() {
        //This section intentionally left blank.
    }

    /**
     * @param sharedSecret The device's shared secret. Non-null value.
     * @param deviceName An arbitrary identifier for the device. Non-null value.
     * @param lastLogin The last login time, for TOTP algorithm usage. Non-null value.
     * @param counter The counter value, for HOTP algorithm usage. Non-null value.
     */
    public OathDeviceSettings(String sharedSecret, String deviceName, long lastLogin, int counter) {
        super();
        setSharedSecret(sharedSecret);
        setDeviceName(deviceName);
        setLastLogin(lastLogin, TimeUnit.SECONDS);
        setCounter(counter);
    }

    /**
     * Set the secret value which is shared between the OATH device and an authenticating module.
     *
     * @param sharedSecret The shared secret. Can not be null.
     */
    public void setSharedSecret(String sharedSecret) {
        Reject.ifNull(sharedSecret, "sharedSecret can not be null.");
        this.sharedSecret = sharedSecret;
    }

    /**
     * Set an arbitrary identifier for the OATH device.
     *
     * @param deviceName The identifier. Can not be null.
     */
    public void setDeviceName(String deviceName) {
        Reject.ifNull(deviceName, "deviceName can not be null.");
        this.deviceName = deviceName;
    }

    /**
     * Set the last login time when this device was used. This is relevant for authentication using
     * the TOTP algorithm.
     *
     * @param lastLogin The last login time in ms. Can not be null.
     * @param timeUnit The time units.
     */
    public void setLastLogin(long lastLogin, TimeUnit timeUnit) {
        Reject.ifNull(lastLogin, "lastLogin can not be null.");
        this.lastLogin = timeUnit.toSeconds(lastLogin);
    }

    /**
     * Set the counter value for this device. This is relevant for authentication using the HOTP algorithm.
     *
     * @param counter The counter value. Can not be null.
     */
    public void setCounter(int counter) {
        Reject.ifNull(counter, "counter can not be null.");
        this.counter = counter;
    }

    /**
     * Set the truncation offset for this device.
     *
     * @param truncationOffset The truncation offset.
     */
    public void setTruncationOffset(int truncationOffset) {
        this.truncationOffset = truncationOffset;
    }

    /**
     * Set the checksum digit for this device.
     *
     * @param checksumDigit The checksum digit.
     */
    public void setChecksumDigit(boolean checksumDigit) {
        this.checksumDigit = checksumDigit;
    }

    /**
     * Get the secret value which is shared between the OATH device and an authenticating module.
     *
     * @return sharedSecret The shared secret.
     */
    public String getSharedSecret() {
        return sharedSecret;
    }

    /**
     * Get the arbitrary identifier for the OATH device.
     *
     * @return deviceName The identifier.
     */
    public String getDeviceName() {
        return deviceName;
    }

    /**
     * Get the last login time, in seconds, when this device was used. This is relevant for authentication using
     * the TOTP algorithm.
     *
     * @return lastLogin The last login time in ms.
     */
    public long getLastLogin() {
        return lastLogin;
    }

    /**
     * Get the counter value for this device. This is relevant for authentication using the HOTP algorithm.
     *
     * @return counter The counter value.
     */
    public int getCounter() {
        return counter;
    }

    /**
     * Get the checksum digit for this device.
     *
     * @return checksumDigit The checksum digit.
     */
    public boolean getChecksumDigit() {
        return checksumDigit;
    }

    /**
     * Get the truncation offset for this device.
     *
     * @return truncationOffset The truncation offset.
     */
    public int getTruncationOffset() {
        return truncationOffset;
    }

    /**
     * The calculated drift between the device and this server, in time steps. Used to implement the
     * resynchronisation protocol described in <a href="https://tools.ietf.org/html/rfc6238#section-6">RFC 6238,
     * section 6</a>.
     *
     * @return the current observed time-step drift for this TOTP client.
     */
    public int getClockDriftSeconds() {
        return clockDriftSeconds;
    }

    /**
     * Sets the observed time-step drift between this device and the server when performing TOTP authentication.
     *
     * @param clockDriftSeconds the observed time drift in time-steps.
     * @see #getClockDriftSeconds()
     */
    public void setClockDriftSeconds(final int clockDriftSeconds) {
        this.clockDriftSeconds = clockDriftSeconds;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        OathDeviceSettings that = (OathDeviceSettings) o;

        if (checksumDigit != that.checksumDigit) {
            return false;
        }
        if (counter != that.counter) {
            return false;
        }
        if (!deviceName.equals(that.deviceName)) {
            return false;
        }
        if (lastLogin  != that.lastLogin) {
            return false;
        }
        if (!sharedSecret.equals(that.sharedSecret)) {
            return false;
        }
        if (truncationOffset != that.truncationOffset) {
            return false;
        }
        if (!uuid.equals(that.getUUID())) {
            return false;
        }
        if (clockDriftSeconds != that.clockDriftSeconds) {
            return false;
        }
        if (!Arrays.equals(recoveryCodes, that.getRecoveryCodes())) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        return Objects.hash(sharedSecret, deviceName, lastLogin, counter,
                checksumDigit, truncationOffset, recoveryCodes, uuid, clockDriftSeconds);
    }

    @Override
    public String toString() {
        return "OathDeviceSettings{" +
                "sharedSecret='" + sharedSecret + '\'' +
                ", deviceName='" + deviceName + '\'' +
                ", lastLogin='" + lastLogin + '\'' +
                ", counter='" + counter + '\'' +
                ", checksumDigit='" + checksumDigit + '\'' +
                ", truncationOffset='" + truncationOffset + '\'' +
                ", UUID='"+ uuid + '\'' +
                ", clockDriftSeconds=" + clockDriftSeconds +
                ", recoveryCodes=" + Arrays.toString(recoveryCodes) +
                '}';
    }

}
